package com.tools.middle.redis.client;

import com.tools.common.exception.DataGenerationException;
import com.tools.common.object.Note;
import com.tools.middle.redis.RedisKit;
import com.tools.middle.redis.RedisSource;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.LinkedHashSet;

/**
 * 自定义封装的 Jedis 包装类
 * 通过 ThreadLocal 和实现
 * Servlet 监听器，在销毁周期方法
 * 里 close 资源，实现了线程安全。
 * 可以设置该类的实例为单例来使用。
 * 【注意】: 该类的后缀带 【$】 的方法可以自动 close。
 * */
@Note("自定义封装的 Jedis 包装类。" +
        "通过 ThreadLocal 和实现 Servlet 监听器，" +
        "在销毁周期方法里 close 资源，实现了线程安全。" +
        "可以设置该类的实例为单例来使用。" +
        "【注意】: 该类的后缀带 【$】 的方法可以自动 close。")
public final class JedisPlus extends TypeZSet implements ServletRequestListener {

    @Note("使用 ThreadLocal 为每个线程维护一个独立的 Jedis 实例")
    private static final ThreadLocal<Jedis> THREADLOCAL = new ThreadLocal<>();

    @Note("Jedis 连接池对象")
    private final JedisPool jedisPool;

    /* **********************************************************************************
     *
     *          构造器
     *
     * *********************************************************************************
     * */

    public JedisPlus(RedisSource source) {
        this.jedisPool = this.private_initSingleNodeAndGetJedisPool(source);
    }

    public JedisPlus(JedisPool jedisPool) {
        if(jedisPool == null) throw new NullPointerException("Jedis 连接池实例为 null");
        this.jedisPool = jedisPool;
    }

    /* **********************************************************************************
     *
     *          实现父类的方法
     *
     * *********************************************************************************
     * */

    @Note("懒加载初始化一个 Jedis 对象")
    @Override
    public Jedis jedis() {
        Jedis jedis = THREADLOCAL.get();
        if(jedis == null) {
            jedis = this.jedisPool.getResource();
            THREADLOCAL.set(jedis);
        }
        return jedis;
    }

    @Note("回收资源")
    @Override
    public void close() {
        closeCurrentThreadJedis();
    }


    @Note("通过静态的方式回收移除当前线程的 Jedis 副本")
    public static void closeCurrentThreadJedis() {
        Jedis jedis = THREADLOCAL.get();
        THREADLOCAL.remove();
        if(jedis != null) {
            jedis.close();
        }
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        this.close();
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        this.close();
    }

    public boolean isInitJedis() {
        return THREADLOCAL.get() != null;
    }



    @Note("加载单机 Redis 节点的信息输出到日志，并根据配置参数和节点信息构建 Jedis 连接池")
    private JedisPool private_initSingleNodeAndGetJedisPool(RedisSource source) {
        if(source == null) throw new NullPointerException("Redis 配置项包装实体为 null，无法根据配置项构建 Jedis 连接池");
        Logger logger = LoggerFactory.getLogger(JedisPlus.class);
        logger.info("正在尝试扫描 Redis 配置项并封装实例...");
        LinkedHashSet<HostAndPort> hostAndPortSet = source.getHostAndPortSet();
        if(hostAndPortSet == null || hostAndPortSet.isEmpty()) {
            String err = "没有扫描到 Redis 节点，请至少配置 1 个以上的 Redis 节点: 【节点ip:节点端口】";
            logger.error(err);
            throw new DataGenerationException(err);
        }
        HostAndPort node;
        if(hostAndPortSet.size() == 1) {
            node = source.firstNode();
            logger.info("扫描到 Redis 单机节点 【{}】，准备配置单机客户端实例...", node);
        } else {
            StringBuilder builder = new StringBuilder();
            for (HostAndPort n : hostAndPortSet) {
                builder.append('\n').append(n.toString());
            }
            node = source.firstNode();
            logger.info("扫描到 Redis 集群节点: {}\n取第 1 个节点 【{}】 来配置单机客户端实例...", builder, node);
        }
        return RedisKit.newJedisPool(source);
    }
}
